package com.nirvana.generate;

import com.nirvana.config.PluginConfig;
import com.nirvana.convert.CamelCaseConverter;
import com.nirvana.data.DependenceData;
import com.nirvana.factory.FlatBuffersResolveFactory;
import com.nirvana.factory.ItemFactory;
import com.nirvana.flatbuffers.IResolve;
import com.nirvana.item.CommonItem;
import com.nirvana.item.DefPropertyItem;
import com.nirvana.type.ItemType;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Set;

import static com.nirvana.config.PluginConfig.PLUGIN_GENERATE_FLAG;

public class BaseDefClassGenerator implements GenerateInterface {
    private static BaseDefClassGenerator INSTANCE;
    private final CommonItem commonItem = ItemFactory.getCommonItem(ItemType.DefPropertyItem);

    private BaseDefClassGenerator() {

    }

    public static BaseDefClassGenerator getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new BaseDefClassGenerator();
        }
        return INSTANCE;
    }

    @Override
    public void generate() {
        StringBuilder content = new StringBuilder();
        StringBuilder packageContent = new StringBuilder();
        StringBuilder importContent = new StringBuilder();
        StringBuilder classContent = new StringBuilder();
        StringBuilder fieldContent = new StringBuilder();
        StringBuilder objectPreContent = new StringBuilder();
        StringBuilder objectContent = new StringBuilder();

        Set<String> hasImport = new HashSet<>();

        generatePackage(packageContent);
        commonItem.itemList.forEach(item -> {
            if (item instanceof DefPropertyItem defPropertyItem) {
                generateImport(importContent, defPropertyItem, hasImport);
                generateField(fieldContent, defPropertyItem);
                generateObject(objectPreContent, objectContent, defPropertyItem);
            }
        });
        if (PluginConfig.getInstance().extendsGroupDef()) {
            importContent.append("""
                    import com.nirvana.net.GameServerHolder
                    """);
            fieldContent.append("""
                        def openGroup: Array[Int]
                        def offGroup: Array[Int]
                        def inGroup: Boolean = {
                            val open_groups = openGroup
                            val off_groups = offGroup
                            if (open_groups.nonEmpty && open_groups.contains(GameServerHolder.getGroupId)) return true
                            if (open_groups.isEmpty && !off_groups.contains(GameServerHolder.getGroupId)) return true
                            false
                        }
                    """);
        }
        generateTrait(classContent, fieldContent);

        String defName = commonItem.textField.getText();
        String filName = CamelCaseConverter.camelToSnakeCase(defName.replace("Def", ""));
        content
                .append(packageContent)
                .append("""
                        import com.google.flatbuffers.FlatBufferBuilder
                        import com.nirvana.bean.flatBuffers.ImplicitConvert._
                        import com.nirvana.bean.xmlBean.%s
                        import com.nirvana.bean.IWriteBinary
                        import com.nirvana.core.ServerConfig
                        import com.nirvana.types.ConfigMode
                        import com.nirvana.xml.XmlConfig
                        import org.apache.commons.codec.digest.DigestUtils
                        
                        import java.nio.channels.FileChannel
                        import java.nio.file.{Files, StandardOpenOption}
                        import scala.collection.mutable.ArrayBuffer
                        """.formatted(defName))
                .append(importContent)
                .append(classContent)
                .append("""
                        //noinspection ScalaWeakerAccess
                        object %sBase extends XmlConfig with IWriteBinary {
                            override protected val fileName = "%s"
                        
                            def load(): Seq[%sBase] = {
                                val mode = ServerConfig.getConfigMode
                                val arr = ArrayBuffer.empty[%sBase]
                                val md5 = FbCfgMetaDefBase.getMd5(fileName)
                                val path = getFbPath(fileName, md5)
                                if (mode == ConfigMode.Xml || mode == ConfigMode.GenFbBin || !Files.exists(path)) {
                                    loadXmlToClassNew[%s](fileName).foreach(m => {
                                        val wrapper = new %sXmlWrapper(m)
                                        addBin(wrapper)
                                        arr += wrapper
                                    })
                                } else {
                                    val buffer = getFbBuffer(path, mode, fileName)
                                    val list = %sFbList.getRootAs%sFbList(buffer)
                                    var i = 0
                                    while (i < list.listLength()) {
                                        val wrapper = new %sFbWrapper(list.list(i))
                                        arr += wrapper
                                        i += 1
                                    }
                                }
                                arr
                            }
                        
                            def addBin(base: %sBase): Unit = {
                                if (ServerConfig.getConfigMode != ConfigMode.GenFbBin) return
                                if (builder == null) {
                                    val len = FbCfgMetaDefBase.getLen(fileName)
                                    builder = new FlatBufferBuilder(len)
                                    offsets = ArrayBuffer.empty[Int]
                                }
                                base match {
                                    case m: %sXmlWrapper =>
                        %s
                                        %sFb.start%sFb(builder)
                        %s
                                        offsets += %sFb.end%sFb(builder)
                                    case _ =>
                                }
                            }
                        
                            override def writeBin(): Unit = {
                                if (ServerConfig.getConfigMode != ConfigMode.GenFbBin) return
                                if (offsets.isEmpty) return

                                val listOffset = offsets.toArray
                                val defsOffset = %sFbList.createListVector(builder, listOffset)
                                %sFbList.start%sFbList(builder)
                                %sFbList.addList(builder, defsOffset)
                                val rootOffset = %sFbList.end%sFbList(builder)
                                builder.finish(rootOffset)
                                val buffer = builder.dataBuffer()
                                val md5 = DigestUtils.md5Hex(buffer.duplicate().array())
                                val path = getFbSavePath(fileName, md5)
                                FileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.WRITE).write(buffer)
                                FbCfgMetaDefBase.addBin(fileName, md5, builder.offset())
                        
                                builder = null
                                offsets = null
                            }
                        
                            override def init(): Unit = {}
                        }
                        %s""".formatted(
                                defName,
                                filName,
                                defName,
                                defName,
                                defName,
                                defName,
                                defName,
                                defName,
                                defName,
                                defName,
                                defName,
                                objectPreContent,
                                defName,
                                defName,
                                objectContent,
                                defName,
                                defName,
                                defName,
                                defName,
                                defName,
                                defName,
                                defName,
                                defName,
                                PLUGIN_GENERATE_FLAG
                ));

        String flatBuffersBasePath = PluginConfig.getInstance().flatBuffersBasePath();
        Path basePath = Paths.get(flatBuffersBasePath);
        try {
            if (Files.notExists(basePath)) {
                Files.createDirectories(basePath);
            }
            Path path = Paths.get(flatBuffersBasePath + "/" + defName + "Base.scala");
            Files.writeString(path, content.toString());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void generatePackage(StringBuilder packageContent) {
        packageContent.append("""
                package %s
                
                """.formatted(PluginConfig.getInstance().flatBuffersDotPackage()));
    }

    private void generateImport(StringBuilder importContent, DefPropertyItem item, Set<String> hasImport) {
        if (!item.isResolve()) return;

        String propertyType = item.getPropertyType();
        String[] types = propertyType.replaceAll("[^a-zA-Z0-9]", " ").split("\\s+");
        for (String type : types) {
            if (hasImport.contains(type) || StringUtils.isBlank(type)) continue;

            DependenceData dependenceDataByType = PluginConfig.getInstance().getDependenceDataByType(type);

            if (dependenceDataByType == null) continue;

            // 导入依赖类
            if (dependenceDataByType.canImport()) {
                importContent.append("""
                import %s
                """.formatted(dependenceDataByType.getFullClassName()));
            }
            hasImport.add(type);
        }

    }

    private void generateTrait(StringBuilder classContent, StringBuilder fieldContent) {
        classContent.append("""
                
                trait %sBase {
                %s
                }
                
                """.formatted(
                commonItem.textField.getText(),
                fieldContent.toString()
        ));
    }

    private void generateObject(StringBuilder objectPreContent, StringBuilder objectContent, DefPropertyItem item) {
        String defName = commonItem.textField.getText();
        String propertyType = item.getPropertyType();
        String propertyName = item.getPropertyName();
        IResolve resolver = FlatBuffersResolveFactory.getResolver(propertyType);
        if (resolver == null) return;

        String offset = resolver.resolve(objectPreContent, defName, propertyName, propertyType);
        if (StringUtils.isBlank(offset)) return;

        objectContent.append("""
                                %sFb.add%s(builder, %s)
                """.formatted(defName, CamelCaseConverter.toCamelCase(propertyName, false), offset));
    }

    private void generateField(StringBuilder fieldContent, DefPropertyItem item) {
        String defName = commonItem.textField.getText();
        String propertyType = item.getPropertyType();
        String propertyName = item.getPropertyName();
        String comment = item.getComment();
        fieldContent.append("""
                    /** %s */
                    def %s: %s
                """.formatted(comment, propertyName, propertyType));
        IResolve resolver = FlatBuffersResolveFactory.getResolver(propertyType);
        if (resolver == null) return;

        resolver.resolveBaseField(fieldContent, defName, comment, propertyName, propertyType);
    }

}
